﻿
using DeepZoomPivotConstructor.ErrorAndLoggingInterfaces;
using DeepZoomPivotConstructor.PivotLib;
using DeepZoomPivotConstructor.VisualCacheInterfaces;
using DeepZoomPivotConstructor.VisualCacheXMLDirectory;
using DeepZoomPivotConstructor.VisualLib;
using PivotData;
using PivotIO.XML;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;

namespace BuildPivotCollection
{
    static class Program
    {
        /// <summary>
        /// Given the directory location of one of the pivot data libraries, we will
        /// render it into a complete pivot view.
        /// 
        /// Usage: BuildPivotCollection <collection-root-directory> <collection-id>  <output-root-pivot-data-directory>
        /// 
        /// The collection directory contains the images.xml file. The output-root-pivot-data-directory should be where the cxml file will be written.
        /// All other files will be written as a subdirectory of that guy. Further, note that the system is "smart" about doing updates to the
        /// images that are already there, so that can improve the speed if you update a directory.
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            if (args.Length != 3)
                throw new InvalidOperationException("Usage: BuildPivotCollection <collection-root-directory> <collection-id>  <output-root-pivot-data-directory>");

            var collectionDB = new DirectoryInfo(args[0]);
            var collectionID = int.Parse(args[1]);
            var pivotDir = new DirectoryInfo(args[2]);

            ConfigurationManager.AppSettings["ROOTXMLDirectory"] = collectionDB.FullName;

            var imagesDB = new DirectoryInfo(Path.Combine(Utils.GetROOTDataDirectory().FullName, collectionID.ToString()));

            if (!imagesDB.Exists)
                throw new DirectoryNotFoundException(string.Format("Image directory {0} was not found.", imagesDB.FullName));
            if (!collectionDB.Exists)
                throw new FileNotFoundException(string.Format("Collection directory {0} was not found.", collectionDB.FullName));
            if (!pivotDir.Exists)
                pivotDir.Create();

            //
            // Load up the collection database and get what we need from it.
            //

            var cinfo = CollectionInfoIO.LoadCollection(collectionID);

            //
            // First step is to load up the iamges and facets database.
            //

            var images = ImageInfoIO.LoadAllImages(collectionID);

            //
            // Now, build up the image database for the pivot builder.
            //


            var vc = new VisualCacheDirectoryDB(new DirectoryInfo(Path.Combine(pivotDir.FullName, "CacheDB")));
            var pc = new PivotCollection();
            var err = new ErrLogger();
            foreach (var pi in images.Select(i => ConvertToPI(i, imagesDB, vc, err, cinfo)).Where(i => i != null))
            {
                pc.Add(pi);
            }

            //
            // Set up facet config. If we are trying to add a facet that isn't known, we
            // just plow on by and ignore it, Just means that the user sent up something extra.
            //

            if (cinfo.WriteImageLink)
            {
                pc.FacetCatagories[cinfo.ImageLinkFacetName].IsFilterVisible = false;
                pc.FacetCatagories[cinfo.ImageLinkFacetName].IsWordWheelVisible = false;
            }

            var fc = FacetCategoryIO.LoadAllFacets(collectionID);
            foreach (var f in fc)
            {
                if (!string.IsNullOrWhiteSpace(f.Name))
                {
                    try
                    {
                        pc.FacetCatagories[f.Name].IsFilterVisible = f.IsFilterVisible;
                        pc.FacetCatagories[f.Name].IsMetaDataVisible = f.IsMetaDataVisible;
                        pc.FacetCatagories[f.Name].IsWordWheelVisible = f.IsWordWheelVisible;
                        pc.FacetCatagories[f.Name].NumberFormat = f.NumberFormat;
                    }
                    catch (KeyNotFoundException)
                    { }
                }
            }

            //
            // If there is any classification data, get that ready to run for the classifier.
            //

            var classFacets = cinfo.Properties.Where(p => p.key == "PartitionByFacet").Select(p => p.value).FirstOrDefault();
            Func<PivotImage, string[]> classifierLambda = null;
            if (classFacets != null && classFacets.Length > 0)
            {
                var splitFacets = classFacets.Split(',').Select(s => s.Trim()).ToArray();
                classifierLambda = img => ClassifyByFacets(img, splitFacets);
            }

            //
            // Next, we need to actually generate the cxml, which will take some time, I guess...
            //

            try
            {
                var cp = new PivotProcessor(err, vc);
                cp.CreatePivotCollection(pc, pivotDir, cinfo.Name, classifierLambda).Wait();
            }
            catch (Exception e)
            {
                Console.WriteLine("Failed to create collection: {0}", e.Message);
                Console.WriteLine("stack: {0}", e.StackTrace);
            }
            catch
            {
                Console.WriteLine("Failed to create a collection for unknown reason!");
            }
        }

        /// <summary>
        /// Classify each image as requested.
        /// </summary>
        /// <param name="img"></param>
        /// <param name="splitFacets"></param>
        /// <returns></returns>
        private static string[] ClassifyByFacets(PivotImage img, string[] splitFacets)
        {
            var facets = from f in img
                         where splitFacets.Contains(f.Name)
                         where f is PivotMetadataString
                         select (f as PivotMetadataString).Value;
            var r = facets.ToArray();
            if (r.Length == 0)
                return new string[] { "None" };
            return r;
        }

        /// <summary>
        /// Calc the pivot meta data that we will use to store for a file.
        /// </summary>
        /// <param name="fi"></param>
        /// <returns></returns>
        private static string CalcPivotMetaData(this FileInfo fi)
        {
            return string.Format("{0} - {1} - {2}", fi.Name, fi.LastWriteTime.ToLongDateString(), fi.LastWriteTime.ToLongTimeString());
        }

        /// <summary>
        /// Convert our image into a pivot image
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        private static PivotImage ConvertToPI(ImageInfo arg, DirectoryInfo imageDir, IVisualCacheDB cache, IErrorLogger err, CollectionInfo cinfo)
        {
            //
            // Have we done this guy already? If it is in our cache, we shouldn't re-deep zoom it! On the other hand,
            // if it has not been done yet, then we need to re-run it.
            //

            var fi = new FileInfo(Path.Combine(imageDir.FullName, arg.Id.ToString()) + string.Format(".{0}", arg.Extension));
            var fileMetaData = fi.CalcPivotMetaData();
            var fileMetaDataHash = fileMetaData.GetHashCode();

            var ch = cache.FindHit(fileMetaDataHash);
            Visual v;
            if (ch != null && ((string)ch.ComposeInfo.MetaData) == fileMetaData)
            {
                v = new VisualItemPlaceHolder(ch.ComposeInfo.Width, ch.ComposeInfo.Height, ch.DZXMLFile, ch.Metadata, fi);
            }
            else
            {
                var vi = new VisualSingleImage(fi, 300);

                vi.SetAsComposableUnit(fileMetaData);
                vi.CompositionInfo.Hash = fileMetaDataHash;
                var bb = vi.VisualBoundingBox;
                vi.CompositionInfo.Width = bb.Width;
                vi.CompositionInfo.Height = bb.Height;
                vi.CompositionInfo.MetaData = fileMetaData;

                v = vi;
            }

            //
            // Now that we have the image, create a pivot image
            //
            //  The standard PV in SL5 can't deal with a facet named "Name" or "name" - in fact, it just fails when this happens.
            // So we special case here. If there is a facet with a name or description as the name, (and a string facet) then we will
            // put it in correctly to the general pivot image metadata.
            //

            var pi = new PivotImage(v) { Name = arg.Name };
            if (arg.Facets != null)
                foreach (var f in arg.Facets)
                {
                    if (f is FacetString)
                    {
                        if (f.Name.ToUpper() == "NAME")
                        {
                            pi.Name = ((FacetString)f).Value;
                        }
                        else if (f.Name.ToUpper() == "DESCRIPTION")
                        {
                            pi.Description = ((FacetString)f).Value;
                        }
                        else
                        {
                            pi.Add(f.Name, ((FacetString)f).Value);
                        }
                    }
                    else if (f is FacetNumber)
                    {
                        // If we try to convert a number larger than a decimal, we will crash here.
                        var fnum = ((FacetNumber)f).Number;
                        if (fnum != double.NaN
                            && fnum < (double)decimal.MaxValue
                            && fnum > (double)decimal.MinValue)
                        {
                            pi.Add(f.Name, (decimal)fnum);
                        }
                    }
                    else if (f is FacetDateTime)
                    {
                        pi.Add(f.Name, (f as FacetDateTime).Date);
                    }
                    else if (f is FacetUri)
                    {
                        pi.Add(f.Name, (f as FacetUri).LinkString, (f as FacetUri).URI);
                    }
                    else
                    {
                        err.LogMessage("Facets", string.Format("Facet {0} is of unknown type {1}. Please update code!", f.Name, f.GetType().Name));
                    }
                }

            if (cinfo.WriteImageLink)
            {
                pi.Add(cinfo.ImageLinkFacetName, cinfo.ImageLinkName, new Uri(string.Format("http://deeptalk.phys.washington.edu/PivotWeb/api/Images?cId={0}&iId={1}", cinfo.Id, arg.Id)));
            }
            return pi;
        }

        class ErrLogger : IErrorLogger
        {
            public void LogMessage(string ID, string message)
            {
                Console.WriteLine("{0} - {1}", ID, message);
            }

            public void LogMessage(string ID, string message, Exception e)
            {
                Console.WriteLine("{0} - {1} - {2} - {3}", ID, message, e.Message, e.StackTrace);
            }

            public void SetLogfileLocation(DirectoryInfo newDirectory)
            {
            }
        }

    }
}
